<template>
  <div class="relative" ref="containerTarget" :style="{ height: containerHeight + 'px' }">
    <!-- 数据渲染 -->
    <template v-if="columnWidth && data.length">
      <div
        v-for="(item, index) in data"
        :key="nodeKey ? item[nodeKey] : index"
        class="m-waterfall-item absolute duration-300"
        :style="{ width: columnWidth + 'px', left: item._style?.left + 'px', top: item._style?.top + 'px' }"
      >
        <slot :item="item" :width="columnWidth" :index="index" />
      </div>
    </template>
    <!-- 加载中 -->
    <!-- <div v-else class="text-zinc-400 text-base">加载中...</div> -->
  </div>
</template>

<script setup>
import { useStore } from 'vuex'
import { computed, nextTick, onMounted, watch, ref, onUnmounted } from 'vue'
import { getImgElements, getAllImg, onComplateImgs, getMinHeightColumn, getMinHeight, getMaxHeight } from './utils'

const store = useStore()

const props = defineProps({
  // 数据源
  data: {
    type: Array,
    required: true
  },
  // 唯一标识
  nodeKey: {
    type: String
  },
  // 列数
  column: {
    default: 2,
    type: Number
  },
  // 列间距
  columnSpacing: {
    default: 20,
    type: Number
  },
  // 行间距
  rowSpacing: {
    default: 20,
    type: Number
  },
  // 是否需要进行图片预读取
  picturePreReading: {
    type: Boolean,
    default: true
  }
})

// 容器的总高度 = 最高的这一列的高度
const containerHeight = ref(0)

// 记录每列高度容器
const columnHeightObj = ref({})
/**
 * 构建记录每列高度的对象
 */
const useColumnHeightObj = () => {
  columnHeightObj.value = {}
  for (let i = 0; i < props.column; i++) {
    columnHeightObj.value[i] = 0
  }
}

// 容器实例
const containerTarget = ref(null)
// 容器总宽度 (不包含 padding、margin、border)
const containerWidth = ref(0)
// 容器左边距,计算 item 的 left
const containerLeft = ref(0)
/**
 * 计算容器宽度
 */
const useContainerWidth = () => {
  const { paddingLeft, paddingRight } = getComputedStyle(containerTarget.value, null)
  // 容器的左边距
  containerLeft.value = parseFloat(paddingLeft)
  // 容器总宽度
  containerWidth.value = containerTarget.value.offsetWidth - parseFloat(paddingLeft) - parseFloat(paddingRight)
}

// 列宽 = (容器的宽度 - 所有的列间距宽度) / 列数
const columnWidth = ref(0)
// 列间距的合计
const columnSpacingTotals = computed(() => {
  return (props.column - 1) * props.columnSpacing
})
/**
 * 计算列宽
 */
const useColumnWidth = () => {
  // 计算容器宽度
  useContainerWidth()
  // 计算列宽
  columnWidth.value = (containerWidth.value - columnSpacingTotals.value) / props.column
}

// 元素加载开始执行
onMounted(() => {
  useColumnWidth()
})

// item 高度集合
let itemHeights = []
/**
 * 监听图片加载完成 【需要图片预加载】 - A方案
 */
const waitImgComplate = () => {
  itemHeights = []
  // 拿到所有的元素
  let itemElements = [...document.getElementsByClassName('m-waterfall-item')]
  setTimeout(() => {
    // 获取到所有图片元素 (通过 img 标签)
    const imgElements = getImgElements(itemElements)
     // 获取到所有图片元素的 src
    const allImgs = getAllImg(imgElements)
    // 等待图片加载完成
    onComplateImgs(allImgs).then(() => {
        // 图片加载完成
        itemElements.forEach((el) => {
        itemHeights.push(el.offsetHeight)
        })
        // 渲染位置
        useItemLocation()
    })
  }, 100);
}

/**
 * 监听图片加载完成 【不需要图片预加载】 - B方案
 */
const useItemHeight = () => {
  itemHeights = []
  // 拿到所有的元素
  let itemElements = [...document.getElementsByClassName('m-waterfall-item')]
  // 计算 item 高度
  itemElements.forEach((el) => {
    itemHeights.push(el.offsetHeight)
  })
  // 渲染位置
  useItemLocation()
}

/**
 * 渲染位置
 */
const useItemLocation = () => {
  // 遍历数据源
  props.data.forEach((item, index) => {
    // 避免重复计算
    if (item._style) {
      return
    }
    // 生产style属性
    item._style = {}
    // left
    item._style.left = getItemLeft()
    // top
    item._style.top = getItemTop()
    // 指定的列高度的自增
    increasingHeight(index)
  })
  // 指定容器的高度自增
  containerHeight.value = getMaxHeight(columnHeightObj.value)
}

/**
 * 在组件销毁时,清除所有的_style
 */
onUnmounted(() => {
  props.data.forEach((item) => {
    delete item._style
  })
})

/**
 * 返回下一个 item 的 left
 */
const getItemLeft = () => {
  // 拿到最小高度的列
  const minColumn = getMinHeightColumn(columnHeightObj.value)
  return minColumn * (columnWidth.value + props.columnSpacing) + containerLeft.value
}

/**
 * 返回下一个 item 的 top
 */
const getItemTop = () => {
  return getMinHeight(columnHeightObj.value)
}

/**
 * 指定列高度自增
 */
const increasingHeight = (index) => {
  // 最小高度所在的列
  const minHeightColumn = getMinHeightColumn(columnHeightObj.value)
  // 使该列自增
  columnHeightObj.value[minHeightColumn] += itemHeights[index] + props.columnSpacing
}

/**
 * 触发计算
 */
watch(
  () => props.data,
  (newVal) => {
    // 重置数据源
    const resetColumnHeight = newVal.every((item) => !item._style)
    if (resetColumnHeight) {
      // 构建高度记录容器
      useColumnHeightObj()
    }
    nextTick(() => {
      if (props.picturePreReading) {
        waitImgComplate()
      } else {
        useItemHeight()
      }
    })
  },
  {
    immediate: true,
    deep: true,
  }
)

/**
 * 重新构建瀑布流
 */
const reset = () => {
  setTimeout(() => {
    // 重新计算列宽
    useColumnWidth()
    // 重置所有的定位数据
    props.data.forEach((item) => {
      item._style = null
    })
  }, 100)
}

/**
 * 监听列数的变化
 */
watch(
  () => props.column,
  () => {
    if (props.picturePreReading) {
      // 数据改变，视图改变的回调
      columnWidth.value = 0
      reset()
    } else {
      reset()
    }
  }
)
</script>
<style scoped lang="scss"></style>
